import { BlockNoteView } from "@blocknote/shadcn";
import {
  createReactBlockSpec,
  createReactInlineContentSpec,
  SuggestionMenuController,
  useCreateBlockNote,
  getDefaultReactSlashMenuItems,
  type DefaultReactSuggestionItem
} from "@blocknote/react";
import "@blocknote/shadcn/style.css";
import { zh } from "@blocknote/core/locales";
import {
  BlockNoteSchema,
  defaultBlockSpecs,
  defaultInlineContentSpecs,
  defaultStyleSpecs,
  filterSuggestionItems,
  insertOrUpdateBlock
} from "@blocknote/core";
import { CustomSocketProvider } from "./CustomSocketProvider";
import * as Y from "yjs";
import { cursorRender } from "../cursorRender";
import { useParams } from "react-router-dom";
import { useEffect, useMemo, useRef, useState } from "react";
import { getSnapshot, getUpdates, saveSnapshot, addUpdate, cleanupOldUpdates } from "../../utils/indexeddb";
import { DocBroadcast } from "../../utils/broadcast";

// 按文档实例化独立的 Y.Doc（避免不同文档共用同一个 Y.Doc 导致内容错乱/空白）

const schema = BlockNoteSchema.create({
  blockSpecs: {
    ...defaultBlockSpecs,
    blockAI: createReactBlockSpec(
      {
        type: "blockAI",
        content: "none",
        propSchema: {
          id: {
            default: "ai-block"
          },
          title: {
            default: "AI Block"
          },
          description: {
            default: "这是一个AI块的描述."
          },
          content: {
            default: "AI生成的内容"
          }
        }
      },
      {
        render: props => {
          const { id, title, description, content } = props.block.props;
          return (
            <div className="p-4 border rounded-lg shadow-md bg-gray-50 w-lg">
              <h3 className="text-lg font-semibold text-blue-600">{title}</h3>
              <p className="text-xs text-gray-500">{description}</p>
              <div className="mt-2 p-2 bg-white border rounded text-lg text-black">
                <span>内容:</span>
                {content}
              </div>
            </div>
          );
        }
      }
    ),
    blockSignature: createReactBlockSpec(
      {
        type: "blockSignature",
        content: "none",
        propSchema: {
          id: {
            default: "signature-block"
          },
          name: {
            default: "签名"
          }
        }
      },
      {
        render: props => {
          const { name } = props.block.props;
          return (
            <div className="flex item-center justify-between p-2 border rounded shadow-md bg-gray-100">
              <span className="text-sm font-semibold text-gray-600">{name}</span>
            </div>
          );
        }
      }
    )
  },
  inlineContentSpecs: {
    ...defaultInlineContentSpecs,
    mention: createReactInlineContentSpec(
      {
        type: "mention",
        content: "none",
        propSchema: {
          id: {
            default: ""
          },
          name: {
            default: ""
          }
        }
      },
      {
        render: props => {
          // mention 行内节点渲染（演示用）
          const { id } = props.inlineContent.props;
          return <span className="text-blue-500 bg-amber-200">@{id}</span>;
        }
      }
    )
  },
  styleSpecs: {
    ...defaultStyleSpecs
  }
});

// 生成随机颜色
function getRandomColor() {
  return `#${Math.floor(Math.random() * 0xffffff)
    .toString(16)
    .padStart(6, "0")}`;
}

// 生成随机用户名
function getRandomName() {
  return `user-${Math.random().toString(36).slice(2, 8)}`;
}

export function DocEditorDemo() {
  const params = useParams();
  const documentId: string = params.id || "";
  // 每个文档使用独立的 Y.Doc，避免多个文档之间状态串扰或残留
  const ydoc = useMemo(() => new Y.Doc(), [documentId]);

  // 抑制标志：用于区分"程序性应用更新（applyUpdate）"与"用户编辑触发的更新"，避免 BC 循环风暴
  const suppressRef = useRef(false);

  // BroadcastChannel 选主：仅 Leader 连接 Socket、负责入库/清理；Follower 仅上报本地增量
  const [isLeader, setIsLeader] = useState(false);
  const bcRef = useRef<DocBroadcast | null>(null);

  // Worker：后台构建、生成快照
  const workerRef = useRef<Worker | null>(null);

  // 根据 isLeader/documentId/ydoc 动态创建/销毁 provider，避免首次渲染拿不到 provider
  // 用途：
  // - 只有 Leader 创建并保持一个 WebSocket provider（CustomSocketProvider），避免同机重复连接/上报。
  // - Follower 返回 null（BlockNote 仍然可本地编辑，通过 BroadcastChannel 与 Leader 同步）。

  const provider = useMemo(() => {
    console.log(isLeader, "isLeader");
    if (!documentId || !isLeader) return null;
    return new CustomSocketProvider("http://localhost:7002", documentId, ydoc);
  }, [isLeader, documentId, ydoc]);
  // 清理：当依赖变化导致 provider 被替换或组件卸载时，销毁旧的 provider 断开连接。
  useEffect(() => {
    return () => {
      try {
        (provider as any)?.destroy?.();
      } catch {}
    };
  }, [provider]);

  // 由服务端权限驱动的可编辑状态（默认允许编辑；服务端可下发只读）
  const [editable, setEditable] = useState(true);

  const editor = useCreateBlockNote({
    schema,
    dictionary: zh,
    // === 协同编辑配置 ===
    collaboration: {
      // WebSocket 提供者：仅 Leader 连接；Follower 本地编辑并通过 BroadcastChannel 协同
      // 传入 undefined 表示不启用 provider（Follower 情况），但编辑器仍可本地编辑
      // @ts-ignore
      provider: provider || undefined,

      // 片段选择：作为 BlockNote 文档的协同根。需与服务端或历史保持一致。
      // 如果你的历史数据是保存在 "blocknote" 片段，请使用下行注释中的片段名。
      // fragment: ydoc.getXmlFragment("blocknote"),
      // 本项目示例按文档 ID 区分片段（示例二选一，确保前后端一致）
      fragment: ydoc.getXmlFragment(`document-${documentId}`),
      // 当前用户信息：用于显示光标和用户状态
      user: {
        name: getRandomName(), // 显示在光标旁边的用户名
        color: getRandomColor() // 光标和选择区域颜色
      },

      // 光标渲染函数：自定义其他用户光标的显示样式
      renderCursor: cursorRender
    }

    // ⚠️ 注意：使用协同编辑时，不要设置 initialContent。内容应该来自协同 provider。
  });

  // 启动 Worker + 本地恢复（不加密）：
  // - 先初始化 worker的影子doc
  // - 从 IndexedDB 读取 snapshot + updates
  // - 先在主线程 ydoc 应用 snapshot，再重放 updates（保证用户立即可见）
  // - 同时把 snapshot/updates 发给 Worker 的 build-doc，用于 建立 影子doc 的状态，然后返回数组状态向量
  //   联网状态不会冲突。原因：
  // Yjs 是 CRDT，Y.applyUpdate 幂等、可交换。你先本地恢复（快照+增量）再发 sync-step1，服务端用 stateVector 只回“你缺的”差量（sync-step2）；重复部分不会二次生效。
  // 如果服务端更新比你新，你会收到非空 sync-step2 并应用；如果你本地比服务端新，sync-step2 为空，你继续把本地新增量按正常 update 上行即可，最终一致。

  useEffect(() => {
    if (!documentId) return;
    const worker = new Worker(new URL("../../worker/y-worker.ts", import.meta.url), { type: "module" });

    workerRef.current = worker;

    // 初始化影子文档
    worker.postMessage({ type: "init", payload: { documentId } });

    let aborted = false;
    (async () => {
      try {
        // 根据文档id 读取本地 文档快照
        const snapshot = await getSnapshot(documentId);

        // 根据文档id 读取本地文档 增量
        const updates = await getUpdates(documentId);

        if (aborted) return;

        if (snapshot) Y.applyUpdate(ydoc, snapshot); // 先应用快照

        for (const up of updates) Y.applyUpdate(ydoc, up); // 再重放增量

        // 传给 Worker 后台构建，生成最新 stateVector（供 sync-step1）
        const transfers: ArrayBuffer[] = [];

        if (snapshot) transfers.push(snapshot.buffer);

        updates.forEach(u => transfers.push(u.buffer));

        // 后台worker 构建最新的快照 snapshot + updates 合并
        worker.postMessage(
          { type: "build-doc", payload: { snapshot: snapshot?.buffer, updates: updates.map(u => u.buffer) } },
          transfers
        );
      } catch (e) {
        console.warn("IndexedDB 恢复失败", e);
      }
    })();

    return () => {
      aborted = true;
      try {
        worker.terminate();
      } catch {}
      workerRef.current = null;
    };
  }, [documentId, ydoc]);

  // 多标签选主 + 职责切换： leader的职责：1.向本地数据将写入文档快照，和增量 。2.通知其他页面 将增量应用到文档。
  // 其他页面的职责。：1.监听文档变化 然后通知leader页面 应用文档的增量。然后触发updae事件 写入本地数据库，并 转发给其他页面，应用增量。
  //  worder的职责：1.保存一份影子doc文档 2。向影子文档写入增量 3. 根据影子文档的 数据 定期生成完整快照 发送给 主线程 主线程接受并写入 本地数据的快照。3.主线程接受到快照后 根据策略 清楚本地数据库的增量。
  //  DocBroadcast的职责：1.选出 主页面。2。主页面和其他页面接受和发送 消息。

  // - 通过 BroadcastChannel 在同机多页间选出唯一 Leader
  // - Leader：启动定期生成快照任务，通知worker定期 根据影子doc的增量和快照去生成最新快照，然后通知主线程  将快照写入 本地数据库。
  // 后面会监听文档update事件
  //  a流程：页面文档发送变化 如果是leader则 写入本地数据库增量，然后发送remote-update事件告知其他页面 应用 增量。
  // 页面发送变化，如果不是leader 则发送 local-update 事件告诉leader 文档应用 增量，然后触发update事件 走a流程 向其他页面转发。 会有  suppressRef锁 防止无限触发
  // 如果是leader页面 接受 其他页面发来的local-update 事件  1增量并应用到 doc 2.写入 本地，3。触发文档的update事件通知其他页面 应用增量
  // 如果是其他页面 则监听leader页面发来的 remote-update 事件 应用发来的 增量更新
  //  每次leader 保存快照到本地就 就按策略 清理一下 本地增量！

  useEffect(() => {
    if (!documentId) return;
    const bc = new DocBroadcast(documentId);
    bcRef.current = bc;
    bc.startElection(leader => {
      console.log(leader, "startElection 回调执行");
      // 设置领导
      setIsLeader(leader);

      // leader 负责页面开始  周期性的制作 影子快照 然后通知 主线程 写入数据库。
      if (leader) startSnapshotJob();
      else stopSnapshotJob();
    });

    // 使用 subscribe，不覆盖选举内部的 onmessage。处理本地/远端广播：
    // 订阅 DocBroadcast 内部发出的消息
    // 接受 leader 页面发来的 消息
    const offBC = bc.subscribe(async msg => {
      if (!msg || !("type" in msg)) return;
      if (msg.type === "local-update") {
        // 这里表示 leader 收到 其他页面 doc更新的消息

        if (!isLeader) return; // 仅 Leader 受理 Follower 的本地上报

        const u8 = new Uint8Array(msg.update);
        try {
          // leader 页面应用上 更新，触发 onUpdate 统一回播和入库（删除此处的重复入库）
          // leader 页面应用上 更新 然后publishRemoteUpdate 回播给所有页面 让其他页面更新
          Y.applyUpdate(ydoc, u8);
        } catch {}
        // try {
        // 这个也 是多余的 又 update事件统一处理
        //   await addUpdate(documentId, u8);
        // } catch {}
        // bc.publishRemoteUpdate(msg.update); // 回播给所有页面
        // 这里是多余的  leader页面会触发 update事件 里面会转播给其他页面
        // 已删除：重复的 addUpdate 和 publishRemoteUpdate，由 onUpdate 统一处理
      }

      if (msg.type === "remote-update") {
        // 这里表示 其他页面 接受leader页面的 增量消息 本地doc 应用上增量
        // 来自 Leader 的最终增量：所有页都要应用。
        // 为避免 onUpdate 再次上报，应用前后开启抑制标志。
        const u8 = new Uint8Array(msg.update);
        try {
          suppressRef.current = true;
          Y.applyUpdate(ydoc, u8);
        } catch {
        } finally {
          suppressRef.current = false;
        }
        // Follower 同步影子文档；Leader 已由 onUpdate 发送给 Worker，无需重复
        if (!isLeader) {
          workerRef.current?.postMessage({ type: "apply-remote-update", payload: { update: msg.update } });
        }
        // 通知 worker 影子 doc 应用上增量
        // workerRef.current?.postMessage({ type: "apply-remote-update", payload:
        // { update: msg.update } });
        // 这里也是多余多 worker的影子doc 增量应用已经由 leader页面 进行应用了
      }

      if (msg.type === "awareness") {
        // awareness 跨页桥接：来自其他页面的 awareness 更新
        const u8 = new Uint8Array(msg.update);
        try {
          if (isLeader) {
            // Leader：转发给服务器并回播给其他页面
            if (provider && (provider as any).ws?.readyState === 1) {
              (provider as any).ws.send(u8);
            }
            // 回播给其他页面（除了发送者）
            bcRef.current?.publishAwareness(msg.update);
          } else {
            // Follower：应用到本地 awareness
            if (provider && (provider as any).awareness) {
              const { applyAwarenessUpdate } = await import("y-protocols/awareness");
              applyAwarenessUpdate((provider as any).awareness, u8, "bc-" + Math.random());
            }
          }
        } catch {}
      }
    });

    return () => {
      try {
        offBC?.();
      } catch {}
      bcRef.current?.destroy();
      bcRef.current = null;
    };
  }, [documentId, ydoc, isLeader]);

  // 监听ydoc 文档的 变化。
  // 1. 写入本地增量，
  // 2. 通知worker影子doc 应用增量
  // 3. 如果是leader则通知其他页面 文档应用增量
  // 4. 如果是 其他页面则 将增量发送leader 由leader进行统一处理 和转发
  // 如果第一次进入文档 没有本地存储，则由远程拉取文档数据后，触发这里的update事件，然后写入本地增量 。后面又定期任务制作快照存入本地。
  useEffect(() => {
    const onUpdate = async (u: Uint8Array) => {
      // 抑制标志：如果是程序性应用（如 remote-update），则不处理，避免循环风暴
      // 避免 无限触发 onUpdate 事件
      if (suppressRef.current) return;

      const buf = u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength); // 精确切片，避免脏数据

      // 仅 Leader 写入增量到 IndexedDB（单点入库策略）
      if (isLeader) {
        try {
          await addUpdate(documentId, u);
        } catch {}
      }

      // 通知worker 影子 doc 应用上增量
      workerRef.current?.postMessage({ type: "apply-local-update", payload: { update: buf } });

      if (bcRef.current) {
        if (isLeader) {
          // Leader：立即回播给所有页
          // 通知其他页面 应用上增量
          bcRef.current.publishRemoteUpdate(buf);
        } else {
          // 如果是在 其他页面更新 编辑的文档 则通知 leader 由leader 处理
          // Follower：仅上报给 Leader，由 Leader 统一处理
          bcRef.current.publishLocalUpdate(buf);
        }
      }
    };

    ydoc.on("update", onUpdate);
    return () => {
      ydoc.off("update", onUpdate);
    };
  }, [documentId, ydoc, isLeader]);

  // Awareness 跨页桥接：监听本地 awareness 变化并通过 BroadcastChannel 分发
  useEffect(() => {
    if (!provider || !bcRef.current) return;

    const awareness = (provider as any).awareness;
    if (!awareness) return;

    const onAwarenessUpdate = ({ added, updated, removed }: any) => {
      try {
        // 编码 awareness 状态并发送给其他页面
        const { encodeAwarenessUpdate } = require("y-protocols/awareness");

        const update = encodeAwarenessUpdate(awareness, [...added, ...updated, ...removed]);

        if (isLeader) {
          // Leader：转发给服务器并回播给其他页面
          if ((provider as any).ws?.readyState === 1) {
            (provider as any).ws.send(update);
          }
          bcRef.current?.publishAwareness(update.buffer);
        } else {
          // Follower：仅发送给 Leader
          bcRef.current?.publishAwareness(update.buffer);
        }
      } catch (e) {
        console.warn("awareness bridge failed:", e);
      }
    };

    // 监听 光标变化
    awareness.on("update", onAwarenessUpdate);
    return () => {
      try {
        awareness.off("update", onAwarenessUpdate);
      } catch {}
    };
  }, [provider, isLeader]);

  // 快照与清理（仅 Leader）：
  // - 定时让 Worker 生成快照
  // - 持久化 docs.snapshot 并清理老的 updates（控制本地体积）
  const snapshotTimerRef = useRef<any>(null);

  /**

   *
   * 处理流程：
   * 1) 非 Leader 或 非 snapshot 消息：直接忽略（防止 Follower 重复写库）。
   * 2) 将 snapshot（ArrayBuffer）转为 Uint8Array 后，写入 IndexedDB.docs（saveSnapshot）。
   * 3) 执行 cleanupOldUpdates：清理历史增量，默认保留最近 1000 条或 7 天内的记录（可按需调整策略）。
   *
   * 设计要点：
   * - 只由 Leader 做持久化，避免同机多标签重复写库。
   * - Worker 委托负责“编码为全量包”，主线程只负责入库与清理，保证职责分离。
   */
  const onWorkerMessage = async (e: MessageEvent) => {
    if (e.data?.type !== "snapshot" || !isLeader) return;
    // 收到快照 转换为 Uint8Array 后，写入 IndexedDB.docs（saveSnapshot）。
    const snap = new Uint8Array(e.data.payload.snapshot);

    try {
      // 保存快照
      await saveSnapshot(documentId, snap);

      // 清理历史增量，默认保留最近 1000 条或 7 天内的记录（可按需调整策略）。
      await cleanupOldUpdates(documentId, { keepLast: 1000, maxAgeMs: 7 * 24 * 3600_000 });
    } catch {}
  };

  /**
   * 启动快照任务（仅 Leader 调用）：
   * - 绑定 Worker 的 message 事件处理器（onWorkerMessage）
   * - 每隔固定周期（默认 180000ms，即 3 分钟）向 Worker 发送 make-snapshot
   * - 监听 window.beforeunload，在页面关闭前尽力触发一次快照（浏览器不保证等待完成）
   *
   * 注意：
   * - 调用前会先 stopSnapshotJob()，确保不会重复开启多个定时器或重复绑定监听。
   * - 非 Leader 切换为 Leader 时会调用；Leader 降级为 Follower 时应调用 stopSnapshotJob() 关闭。
   */
  const startSnapshotJob = () => {
    stopSnapshotJob(); // 避免重复定时
    // 监听 worker的快照 制作 制作完后后返回 快照 将快照保存到 IndexedDB.docs
    // 接受 worker 制作快照完成事件
    workerRef.current?.addEventListener("message", onWorkerMessage as any);

    // 定时请求 Worker 生成快照（全量更新包）  定期通知worker 制作快照
    snapshotTimerRef.current = setInterval(() => workerRef.current?.postMessage({ type: "make-snapshot" }), 180000);

    // 页面关闭前尽力做一次快照（不保证完成）
    window.addEventListener("beforeunload", beforeUnloadOnce);
  };

  /**
   * 停止快照任务（Leader->Follower 切换或组件卸载时调用）：
   * - 清除定时器
   * - 解除 Worker 的 message 监听
   * - 移除 beforeunload 监听
   *
   * 注意：
   * - 若 snapshotTimerRef.current 为 null，说明没有开启过定时器，直接跳过清理即可。
   * - 移除监听操作需包裹 try/catch 以防止 Worker 已被 terminate 导致的异常。
   */
  const stopSnapshotJob = () => {
    if (snapshotTimerRef.current) {
      clearInterval(snapshotTimerRef.current);
      snapshotTimerRef.current = null;
    }
    try {
      // 移除 Worker 的 message 监听
      workerRef.current?.removeEventListener("message", onWorkerMessage as any);
    } catch {}
    window.removeEventListener("beforeunload", beforeUnloadOnce);
  };

  const beforeUnloadOnce = () => {
    if (!isLeader) return;
    // 页面关闭前尽力让 Worker 生成一次快照（浏览器不保证等待完成）
    workerRef.current?.postMessage({ type: "make-snapshot" });
  };

  // 监听服务端权限并切换编辑器可编辑状态（仅 Leader 持有 provider）
  // - 初始按 provider 当前状态设置（若服务端已下发）
  // - 订阅 permission 事件，动态切换可编辑
  useEffect(() => {
    const applyEditable = (canWrite: boolean) => {
      setEditable(!!canWrite);
    };

    if (provider) {
      // @ts-ignore
      applyEditable(!!provider.getCanWrite?.() || !!(provider as any).canWrite);
    }

    const off = (provider as any)?.onPermission?.(({ canWrite }: { canWrite: boolean }) => applyEditable(canWrite));
    return () => {
      if (typeof off === "function") off();
    };
  }, [editor, provider]);

  const getMentionUserItems = (e: typeof editor) => {
    const userItems: DefaultReactSuggestionItem[] = [
      {
        icon: <span>👤</span>,
        title: "用户123",
        onItemClick: () => {
          e.insertInlineContent([
            {
              type: "mention",
              props: { id: "User123", name: "用户123" }
            }
          ]);
        }
      },
      {
        icon: <span>👤</span>,
        title: "渡一用户123",
        onItemClick: () => {
          e.insertInlineContent([
            {
              type: "mention",
              props: { id: "User123", name: "用户123" }
            }
          ]);
        }
      }
    ];
    return userItems;
  };

  const insertBlockAI = (e: typeof editor) => {
    return {
      title: "AI助手",
      subtext: "为您提供智能建议",
      icon: <span>🤖</span>,
      onItemClick: () => {
        insertOrUpdateBlock(e, {
          type: "blockAI",
          props: {
            id: `ai-block-${Date.now()}`,
            title: "AI生成的标题",
            description: "AI生成的描述",
            content: "AI生成的内容"
          }
        });
      }
    };
  };

  const insertBlockSignature = (e: typeof editor) => {
    return {
      title: "签名",
      subtext: "为您提供签名块",
      icon: <span>✍️</span>,
      onItemClick: () => {
        insertOrUpdateBlock(e, {
          type: "blockSignature",
          props: {
            id: `signature-block-${Date.now()}`,
            name: "JACK"
          }
        });
      }
    };
  };

  useEffect(() => {
    if (documentId) {
      // 这里可按需加载文档基础信息（标题、权限等）
    }
  }, [documentId]);

  const handleInvite = () => {
    console.log("邀请");
    fetch(`/api/documents/${documentId}/invite`, {
      method: "POST",
      credentials: "include",
      body: JSON.stringify({
        userId: 4,
        permission: "write"
        // write read
      }),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(res => {
        if (!res.ok) {
          throw new Error(`HTTP ${res.status}: ${res.statusText}`);
        }
        return res.json();
      })
      .then(data => {
        console.log("验证响应:", data);
      });
  };

  const handleUndo = () => {
    try {
      editor.undo();
    } catch {}
  };

  const handleRedo = () => {
    try {
      editor.redo();
    } catch {}
  };

  return (
    <>
      <div className="relative">
        <div
          className="w-[40px] h-[30px] text-[16px] bg-[#0ea43b] text-[#e2bc23] text-center leading-[30px] border-w-[1px] border-[#e2bc23] rounded-[10px]"
          onClick={handleUndo}
        >
          撤销
        </div>
        <div
          className="w-[40px] h-[30px] text-[16px] bg-[#0ea43b] text-[#e2bc23] text-center leading-[30px] border-w-[1px] border-[#e2bc23] rounded-[10px]"
          onClick={handleRedo}
        >
          重做
        </div>
        <div
          className="absolute top-[10px] right-[20px] w-[40px] h-[30px] text-[16px] bg-[#0ea43b] text-[#e2bc23] text-center leading-[30px] border-w-[1px] border-[#e2bc23] rounded-[10px] z-1000"
          onClick={handleInvite}
        >
          邀请
        </div>
        <BlockNoteView editor={editor} slashMenu={false} editable={editable}>
          <SuggestionMenuController
            triggerCharacter="@"
            getItems={async query => {
              return filterSuggestionItems(getMentionUserItems(editor), query);
            }}
          />
          <SuggestionMenuController
            triggerCharacter="/"
            getItems={async query => {
              return filterSuggestionItems(
                [insertBlockSignature(editor), insertBlockAI(editor), ...getDefaultReactSlashMenuItems(editor)],
                query
              );
            }}
          />
        </BlockNoteView>
      </div>
    </>
  );
}
